package com.google.code.sysarch.v2.v2_final;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Polygon;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;

public class WayMap {

	HashMap<String,WayPoint> points;
	ArrayList<String> keys;
	ArrayList<PathWalker> objects;
	public boolean drawPaths = false;
	
	WayMap() {
		this.points = new HashMap<String,WayPoint>();
		this.keys = new ArrayList<String>();
		this.objects = new ArrayList<PathWalker>();
	}
	
	public synchronized boolean addPoint(WayPoint point) {
		if (this.points.containsKey(point.getName())) {
			return false;
		}
		this.points.put(point.getName(), point);
		this.keys.add(point.getName());
		return true;
	}
	
	public synchronized boolean addUEdge(String name1, String name2) {
		WayPoint p1 = this.points.get(name1);
		WayPoint p2 = this.points.get(name2);
		if (p1 == null || p2 == null) {
			return false;
		}
		if (p1.getEdges().contains(p2) || p2.getEdges().contains(p1)) {
			return false;
		}
		p1.getEdges().add(p2);
		p2.getEdges().add(p1);
		return true;
	}
	
	public synchronized boolean addDEdge(String name1, String name2) {
		WayPoint p1 = this.points.get(name1);
		WayPoint p2 = this.points.get(name2);
		if (p1 == null || p2 == null) {
			return false;
		}
		if (p1.getEdges().contains(p2)) {
			return false;
		}
		p1.getEdges().add(p2);
		return true;
	}	
	
	public synchronized boolean addObject(Drawable obj, String wpoint) {
		for (PathWalker p : objects) {
			if (p.getObject() == obj) {
				return false;
			}
		}
		WayPoint point = points.get(wpoint);
		if (point == null) { return false; }
		PathWalker p = new PathWalker(obj);
		objects.add(p);
		p.setPosition(point.getX(), point.getY());
		p.setOrigin(point);
		return true;
	}
	
	public synchronized boolean removeObject(Drawable obj) {
		for (int i=0; i<objects.size(); i++) {
			if (objects.get(i).getObject() == obj) {
				objects.remove(i);
				break;
			}
		}
		return true;
	}
	
	public synchronized WayPoint removePoint(String name) {
		WayPoint point = this.points.remove(name);
		LinkedList<WayPoint> edges = point.getEdges();
		for (WayPoint p : edges) {
			p.getEdges().remove(point);
		}
		return point;
	}
	
	public void draw(Graphics2D g) {
		if (keys.isEmpty()) { return; }
		if (drawPaths) { 
			String start = keys.get(0);		
			HashMap<String, Boolean> marked = new HashMap<String, Boolean>();
			draw_paths(g, start, marked);			
		}
		draw_objects(g);
	}	
	
	private void draw_paths(Graphics2D g, String start, HashMap<String, Boolean> marked) {	
		LinkedList<String> searchList = new LinkedList<String>();
		searchList.add(start);
		while (!searchList.isEmpty()) {
			String current = searchList.removeFirst();
			if (!marked.containsKey(current)) {
				marked.put(current, true);				
				WayPoint point = points.get(current);
				g.setColor(Color.RED);
				g.draw(new Rectangle(point.getX()-3, point.getY()-3, 7, 7));
				LinkedList<WayPoint> edges = points.get(current).getEdges();
				for (WayPoint p : edges) {
					boolean isMarked = true;
					if (!marked.containsKey(p.getName())) {
						searchList.add(p.getName());						
						isMarked = false;
					}					
					if (!p.getEdges().contains(point)) {
						g.setColor(Color.BLUE);
						g.draw(new Line2D.Double(point.getX(), point.getY(), p.getX(), p.getY()));
						int x = (int)(point.getX() + (p.getX()-point.getX())*0.2f);
						int y = (int)(point.getY() + (p.getY()-point.getY())*0.2f);
						int[] xpoints = { x, x+3, x-3 };
						int[] ypoints = { y-3, y+3, y+3 };
						g.draw(new Polygon(xpoints, ypoints, 3));
					} else if (!isMarked) {
						g.setColor(Color.RED);
						g.draw(new Line2D.Double(point.getX(), point.getY(), p.getX(), p.getY()));						
					}
				}
			}
		}		
		if (marked.size() < keys.size()) {
			String start2 = "";
			for (String key : keys) {
				if (!marked.containsKey(key)) {
					start2 = key;
					break;
				}
			}
			draw_paths(g, start2, marked);
		}
	}	
	
	@SuppressWarnings("unchecked")
	public void draw_objects(Graphics2D g) {	
		Collections.sort(objects);
		PathWalker p;
		for (int i=0; i<objects.size(); i++) {
			p = objects.get(i);
			p.getObject().draw(g, p.getX(), p.getY());
		}
	}	
	
	public void move(Drawable obj, String target) {		
		for (PathWalker p : objects) {
			if (p.getObject() == obj) {
				// TODO remove this and handle it via destination only
				move(p, points.get(target));
				break;
			}
		}
	}
	
	public void move(PathWalker p, String target) {		
		move(p, points.get(target));
	}	
	
	public void move(PathWalker p, WayPoint target) {		
		// TODO remove this and handle it via destination only
		if (p == null || target == null) {
			System.out.println("Error trying to move!");
			return;
		}
		p.setTarget(target);
		p.setDestination(target);
//		Leave event				
		onEvent(p, p.getOrigin().onLeave(), p.getOrigin(), target);
	}		
		
	public void updateAll(double delta) {		
		for (PathWalker p : objects) {
			updateSingle(p, delta);
		}
	}
	
	public void updateSingle(PathWalker p, double delta) {
		double len;
		double adv;
		double hdiff;
		double vdiff;
		
		WayPoint from = p.getOrigin();
		WayPoint to = p.getTarget();
		if (from == null) {
			Util.printMessage("Origin is Null for PathWalker "+p.toString(), Util.MTYPE_ERROR);
		}
		if (to == null) {
			if (p.getDestination() != null) {
				// TODO implement route searching
			}
		}
		if (to != null) {
			hdiff = (from.getX()-to.getX());
			vdiff = (from.getY()-to.getY());
			len = Math.sqrt(hdiff*hdiff + vdiff*vdiff);		
			adv = p.getAdvance() + (p.getSpeed()*delta)/len;
			p.setPosition((int)(from.getX() - hdiff*adv), (int)(from.getY() - vdiff*adv));
			p.setAdvance(adv);
			if (adv >= 1) {
//				OnEnter event
				onEvent(p, p.getOrigin().onEnter(), to, from);
			} else {
//				OnTravel event
				onEvent(p, "travel", from, to);
			}
		} else {
//			OnStay event			
			onEvent(p, from.onStay(), from, p.getLastVisit());
		}
	}
	
	public synchronized PathWalker getPathWalker(Drawable obj) {
		for (PathWalker p : objects) {
			if (p.getObject() == obj) {
				return p;
			}
		}
		return null;
	}
	
	public void onEvent(PathWalker p, String event, WayPoint caller, WayPoint helper) {
		// No event?
		if (event == null) { return; }
		// Drawable object knows that event?
		if (p.getObject().onEvent(p, event, caller, helper) == true) { return; }
		
		if (event.equals("random")) {
			if (caller.getEdges().isEmpty()) { return; }
			int next = (int)(Math.random()*caller.getEdges().size());
			move(p, caller.getEdges().get(next));
		} else if (event.equals("ahead")) {
			int modifier = 0;
			if (caller.getEdges().isEmpty()) { return; }		
			if (caller.getEdges().contains(helper)) { modifier = -1; }
			int next = (int)(Math.random()*(caller.getEdges().size() + modifier)) + 1;
			for (WayPoint point : caller.getEdges()) {
				if (point != helper) {
					next--;
				}
				if (next == 0) {
					move(p, point);
					break;
				}				
			}
		}
	}
	
}

class WayPoint {
	private LinkedList<WayPoint> edges;
	private String name;
	private int x;
	private int y;
	private String onEnter;
	private String onLeave;
	private String onStay;
	
	public WayPoint(String name, int x, int y) {
		this(name, x, y, null, null, null);
	}

	public WayPoint(String name, int x, int y, String enter) {
		this(name, x, y, enter, null, null);
	}	
	
	public WayPoint(String name, int x, int y, String enter, String leave) {
		this(name, x, y, enter, leave, null);
	}
	
	public WayPoint(String name, int x, int y, String enter, String leave, String stay) {
		this.name = name;		
		this.x = x;
		this.y = y;
		this.onEnter = enter;
		this.onLeave = leave;
		this.onStay = stay;
		this.edges = new LinkedList<WayPoint>();
	}
	
	public void setOnEnter(String event) {
		this.onEnter = event;
	}
	
	public void setOnLeave(String event) {
		this.onLeave = event;
	}
	
	public void setOnStay(String event) {
		this.onStay = event;
	}	
	
	public String onEnter() {
		return this.onEnter;
	}
	
	public String onLeave() {
		return this.onLeave;
	}
	
	public String onStay() {
		return this.onStay;
	}
	
	public String getName() {
		return this.name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public int getX() {
		return x;
	}
	
	public int getY() {
		return y;
	}	
	
	public void setPosition(int x, int y) {
		this.x = x;
		this.y = y;
	}
	
	public LinkedList<WayPoint> getEdges() {
		return this.edges;
	}
	
}

@SuppressWarnings("unchecked")
class PathWalker implements Comparable {
	
	private Drawable object;
	private WayPoint origin;
	private WayPoint target;
	private WayPoint destination;
	private WayPoint lastvisit;
	private int x;
	private int y;
	private double advance;
	private double speed;

	PathWalker(Drawable obj) {
		this.object = obj;
		this.advance = 0;
		this.target = null;
		this.speed = 250;
	}
	
	public Drawable getObject() {
		return this.object;
	}
	
	public void setPosition(int x, int y) {
		this.x = x;
		this.y = y;
	}
	
	public int getX() {
		return this.x;
	}
	
	public int getY() {
		return this.y;
	}
	
	public WayPoint getTarget() {
		return this.target;
	}
	
	public void setTarget(WayPoint target) {
		this.target = target;
	}
	
	public WayPoint getOrigin() {
		return this.origin;
	}
	
	public void setOrigin(WayPoint origin) {
		this.origin = origin;
	}
	
	public WayPoint getDestination() {
		return this.destination;
	}
	
	public void setDestination(WayPoint dest) {
		this.destination = dest;
	}
	
	public WayPoint getLastVisit() {
		return lastvisit;
	}

	public void setLastVisit(WayPoint lastvisit) {
		this.lastvisit = lastvisit;
	}	
	
	public int compareTo(Object obj) {
		PathWalker p = (PathWalker) obj;
		return this.y - p.getY();
	}

	public double getAdvance() {
		return advance;
	}

	public void setAdvance(double advance) {
		this.advance = advance;
		if (this.advance >= 1) {
			this.advance = 0;
			this.lastvisit = this.origin;
			this.origin = this.target;			
			this.target = null;
			this.x = this.origin.getX();
			this.y = this.origin.getY();
		}
	}
	
	public double getSpeed() {
		return speed;
	}

	public void setSpeed(double speed) {
		this.speed = speed;
	}	
	
	public String toString() {
		return "\""+object.toString()+" at \""+origin.getName()+"\"";
	}
	
}

interface Drawable {
	public void draw(Graphics2D g, int x, int y);
	public boolean onEvent(PathWalker p, String event, WayPoint caller, WayPoint helper);
}